package cfg

import (
	"errors"
	"github.com/BurntSushi/toml"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"path/filepath"
	"reflect"
	"strings"
	"time"
)

// 配置对象
type Handler struct {
	Source   string
	ConfType string
	confInfo interface{}
}

// New default get a cfg handler by file path and not open dynamic
func New(filePath string) *Handler {
	return NewFile(filePath, false)
}

// NewFile get a cfg handler by file path
func NewFile(filePath string, dynamic bool) *Handler {
	if filePath == "" {
		return nil
	}

	filePath, err := filepath.Abs(filePath)
	if err != nil {
		panic(err)
	}
	confFileType := strings.Trim(filepath.Ext(filePath), ".")

	handler := Handler{ConfType: confFileType, Source: "file"}
	switch confFileType {
	case "toml":
		if _, err := toml.DecodeFile(filePath, &handler.confInfo); err != nil {
			panic(err)
		}
	case "yml", "yaml":
		yamlConfInfo, err := ioutil.ReadFile(filePath)
		if err != nil {
			panic(err)
		}
		if err := yaml.Unmarshal(yamlConfInfo, &handler.confInfo); err != nil {
			panic(err)
		}
	default:
		panic("can not support this type config file:" + confFileType)
	}

	if dynamic {
		// not support dynamic config
	}
	return &handler
}

// NewTomlData get a cfg handler by toml string content
func NewTomlData(content string) *Handler {
	if content == "" {
		return nil
	}
	handler := Handler{ConfType: "toml", Source: "content"}

	if _, err := toml.Decode(content, &handler.confInfo); err != nil {
		panic(err)
	}
	return &handler
}

// ReloadTomlData get a cfg handler by toml string content
func ReloadTomlData(content string) (*Handler, error) {
	if content == "" {
		return nil, errors.New("config content is empty")
	}
	handler := Handler{ConfType: "toml", Source: "content"}

	if _, err := toml.Decode(content, &handler.confInfo); err != nil {
		return nil, err
	}
	return &handler, nil
}

// NewTomlData get a cfg handler by toml string content
func NewYamlData(content string) *Handler {
	if content == "" {
		return nil
	}
	handler := Handler{ConfType: "yaml", Source: "content"}

	if err := yaml.Unmarshal([]byte(content), &handler.confInfo); err != nil {
		panic(err)
	}
	return &handler
}

// ReloadYamlData get a cfg handler by toml string content
func ReloadYamlData(content string) (*Handler, error) {
	if content == "" {
		return nil, errors.New("config content is empty")
	}
	handler := Handler{ConfType: "yaml", Source: "content"}

	if err := yaml.Unmarshal([]byte(content), &handler.confInfo); err != nil {
		return nil, err
	}
	return &handler, nil
}

// GetString get config string type
func (c *Handler) GetString(key string) string {
	ret := c.ParseNode(key)
	showRet, ok := ret.(string)
	if ok != true {
		return ""
	}
	return showRet
}

// GetInt get config int type
func (c *Handler) GetInt(key string) int {
	ret := c.ParseNode(key)

	if c.ConfType == "toml" {
		showRet, ok := ret.(int64)
		if ok != true {
			return 0
		}
		return int(showRet)
	}

	showRet, ok := ret.(int)
	if ok != true {
		return 0
	}
	return int(showRet)
}

// GetInt64 get config int64 type
func (c *Handler) GetInt64(key string) int64 {
	ret := c.ParseNode(key)
	if c.ConfType == "toml" {
		showRet, ok := ret.(int64)
		if ok != true {
			return 0
		}
		return int64(showRet)
	}

	showRet, ok := ret.(int)
	if ok != true {
		return 0
	}
	return int64(showRet)
}

// GetBool get config bool type
func (c *Handler) GetBool(key string) bool {
	ret := c.ParseNode(key)
	showRet, ok := ret.(bool)
	if ok != true {
		return false
	}
	return showRet
}

// GetDuration get config time.Duration type
func (c *Handler) GetDuration(key string) time.Duration {
	ret := c.GetInt64(key)
	showRet := time.Duration(ret)
	return showRet
}

// GetSliceInt get slice int type
func (c *Handler) GetSliceInt(key string) []int {
	slice := c.getSlice(key)
	showRet := make([]int, 0)
	if len(slice) == 0 {
		return showRet
	}
	for _, item := range slice {
		switch item.(type) {
		case int64:
			value, ok := item.(int64)
			if ok != true {
				break
			}
			showRet = append(showRet, int(value))
		case int:
			value, ok := item.(int)
			if ok != true {
				break
			}
			showRet = append(showRet, int(value))
		}
	}

	return showRet
}

// GetSliceInt64 get slice int64 type
func (c *Handler) GetSliceInt64(key string) []int64 {
	slice := c.getSlice(key)
	showRet := make([]int64, 0)
	if len(slice) == 0 {
		return showRet
	}
	for _, item := range slice {
		switch item.(type) {
		case int64:
			value, ok := item.(int64)
			if ok != true {
				break
			}
			showRet = append(showRet, int64(value))
		case int:
			value, ok := item.(int)
			if ok != true {
				break
			}
			showRet = append(showRet, int64(value))
		}
	}

	return showRet
}

// GetSliceString get slice string type
func (c *Handler) GetSliceString(key string) []string {
	slice := c.getSlice(key)
	showRet := make([]string, 0)
	if len(slice) == 0 {
		return showRet
	}
	for _, item := range slice {
		value, ok := item.(string)
		if ok != true {
			break
		}
		showRet = append(showRet, value)
	}
	return showRet
}

// GetMap parse config node to map
func (c *Handler) GetMap(parse string) map[string]interface{} {
	nodeParse := strings.Split(parse, ".")
	var ret interface{}
	ret = c.confInfo
	for _, item := range nodeParse {
		ret = readNode(item, ret)
	}

	if mapRet, ok := ret.(map[string]interface{}); ok {
		return mapRet
	}
	if mapIRet, ok := ret.(map[interface{}]interface{}); ok {
		mapRet := make(map[string]interface{})
		for k, item := range mapIRet {
			if mapKey, ok := k.(string); ok == true {
				mapRet[mapKey] = item
			}
		}
		return mapRet
	}
	return nil
}

// ParseNode parse config node
func (c *Handler) ParseNode(parse string) (ret interface{}) {
	nodeParse := strings.Split(parse, ".")

	ret = c.confInfo
	for _, item := range nodeParse {
		ret = readNode(item, ret)
	}

	return
}

// Exists find config node exists
func (c *Handler) Exists(parse string) bool {
	nodeParse := strings.Split(parse, ".")
	var ret interface{}
	var exists bool

	ret = c.confInfo
	for _, item := range nodeParse {
		exists, ret = readExistsNode(item, ret)
	}

	return exists
}

// getSlice get config slice []interface{} type
func (c *Handler) getSlice(key string) []interface{} {

	ret := c.ParseNode(key)
	slice, ok := ret.([]interface{})
	if ok != true {
		return []interface{}{}
	}
	return slice
}

// readNode ...
func readNode(key string, node interface{}) (ret interface{}) {
	if node == nil {
		return
	}
	nodeType := reflect.TypeOf(node).String()
	if nodeType == "map[string]interface {}" {
		nodeInfo := node.(map[string]interface{})
		return nodeInfo[key]
	}
	if nodeType == "map[interface {}]interface {}" {
		nodeInfo := node.(map[interface{}]interface{})
		return nodeInfo[key]
	}

	return node
}

// readExistsNode ...
func readExistsNode(key string, node interface{}) (exists bool, ret interface{}) {
	if node == nil {
		return
	}
	nodeType := reflect.TypeOf(node).String()

	if nodeType == "map[string]interface {}" {
		nodeInfo := node.(map[string]interface{})

		if _, ok := nodeInfo[key]; ok {
			return true, nodeInfo[key]
		}
		return false, nodeInfo[key]
	}

	if nodeType == "map[interface {}]interface {}" {
		nodeInfo := node.(map[interface{}]interface{})
		if _, ok := nodeInfo[key]; ok {
			return true, nodeInfo[key]
		}
		return false, nodeInfo[key]
	}

	return false, node
}
